--[[ 编码: JX-60-12 名称: 自动配盘 作者: 日期: 2025-1-29 函数: main 功能: -- 根据出库波次明细计算出配盘及配盘明细,再把出库货品批分到各出库单绑定的拣料箱 备注: -- 该程序起源巨星二期料箱库,适合多料格料箱的出库分拣操作 更改记录: V2.0 合并取消 combine_cntr_detail --]] wms_out = require( "wms_outbound" ) wms_station = require( "wms_station" ) local function get_d_cntr_detail_by_bs_no( d_cntr_detail_list, bs_no ) local sub_d_cntr_detail_list = {} local n for n = 1, #d_cntr_detail_list do if ( d_cntr_detail_list[n].bs_no == bs_no ) then table.insert( sub_d_cntr_detail_list, d_cntr_detail_list[n]) end end return sub_d_cntr_detail_list end -- 根据体积进行排序,体积大的放前面 local function dc_detail_sort_by_volume( t1, t2 ) return t1.volume*t1.qty > t2.volume*t2.qty end local function picking_box_sort_by_volume( t1, t2 ) return t1.box_volume> t2.box_volume end -- d_cntr_detail_list ( 某个出库单相关的配盘明细 ) pick_box_code 拣料箱编码 -- new_d_cntr_detail_list 批分结果 local function split_by_picking_box( strLuaDEID, d_cntr_detail_list, picking_box_code, new_d_cntr_detail_list ) local nRet, strRetInfo local seg = lua.split( picking_box_code, ";" ) local n, m -- 初始化拣料箱 local picking_box_list = {} local box_volume = wms_base.Get_nConst( strLuaDEID, "拣料箱体积") local pick_box_num = #seg for n = 1, pick_box_num do local picking_box = { picking_box_code = seg[n], box_volume = box_volume, } table.insert( picking_box_list, picking_box ) end -- 把体积大的放前面 table.sort( d_cntr_detail_list, dc_detail_sort_by_volume ) -- 批分到不同的拣料箱 local find, volume, qty, need_split_qty for n = 1, #d_cntr_detail_list do find = false volume = d_cntr_detail_list[n].qty*d_cntr_detail_list[n].volume for m = 1, pick_box_num do if ( volume <= picking_box_list[m].box_volume ) then d_cntr_detail_list[n].pick_box_code = picking_box_list[m].picking_box_code picking_box_list[m].box_volume = picking_box_list[m].box_volume - volume find = true break end end if ( find ) then table.insert( new_d_cntr_detail_list, d_cntr_detail_list[n] ) else -- 需要批分,把一条【配盘明细】根据拣料箱 need_split_qty = d_cntr_detail_list[n].qty -- 需要批分的数量 for m = 1, pick_box_num do if ( picking_box_list[m].box_volume > 0 ) then qty = math.floor( picking_box_list[m].box_volume/ d_cntr_detail_list[n].volume ) if ( need_split_qty < qty ) then qty = need_split_qty end need_split_qty = need_split_qty - qty local d_cntr_detail = { item_code = d_cntr_detail_list[n].item_code, item_name = d_cntr_detail_list[n].item_name, cntr_code = d_cntr_detail_list[n].cntr_code, batch_no = d_cntr_detail_list[n].batch_no, cell_no = d_cntr_detail_list[n].cell_no, station = d_cntr_detail_list[n].station, serial_no = d_cntr_detail_list[n].serial_no, item_spec = d_cntr_detail_list[n].item_spec, end_user = d_cntr_detail_list[n].end_user, owner = d_cntr_detail_list[n].owner, uom = d_cntr_detail_list[n].uom, volume = d_cntr_detail_list[n].volume, weight = d_cntr_detail_list[n].weight, wh_code = d_cntr_detail_list[n].wh_code, area_code = d_cntr_detail_list[n].area_code, loc_code = d_cntr_detail_list[n].loc_code, cg_detail_id = d_cntr_detail_list[n].cg_detail_id, bs_type = d_cntr_detail_list[n].bs_type, bs_no = d_cntr_detail_list[n].bs_no, bs_row_no = d_cntr_detail_list[n].bs_row_no, wave_no = d_cntr_detail_list[n].wave_no, wave_cls_id = d_cntr_detail_list[n].wave_cls_id, pick_box_code = picking_box_list[m].picking_box_code, qty = qty } picking_box_list[m].box_volume = picking_box_list[m].box_volume - qty*d_cntr_detail_list[n].volume table.insert( new_d_cntr_detail_list, d_cntr_detail ) end if ( need_split_qty == 0 ) then break end end -- 如果还有 need_split_qty 说明无法分配拣货箱,这是有问题的需要报警 if ( need_split_qty > 0 ) then return 1, "货品'"..d_cntr_detail_list[n].item_code.."'因为体积的原因无法分配拣料箱!" end end -- 把拣料箱可用容积最大的放前面 table.sort( picking_box_list, picking_box_sort_by_volume ) end return 0 end -- 把配盘明细中相同料格的货品数量进行合并 -- Distribution_CNTR_Detail 合并,在出库的时候一个料格可能会有多条CG_Detail local function combine_cntr_detail( d_cntr_detail_list ) local n local combine_result = {} for n = 1, #d_cntr_detail_list do find = false for m = 1, #combine_result do if ( d_cntr_detail_list[n].cntr_code == combine_result[m].cntr_code and d_cntr_detail_list[n].cell_no == combine_result[m].cell_no ) then if ( d_cntr_detail_list[n].item_code ~= combine_result[m].item_code ) then return 1, "合并配盘明细发生错误, 相同的料格存在不同编码的货品!" end find = true combine_result[m].qty = combine_result[m].qty + d_cntr_detail_list[n].qty -- MDF BY HAN 20250314 combine_result[m].cg_detail_id = "" -- 多条合并后CG_Detail要设置为空 end end if ( find == false ) then table.insert( combine_result, d_cntr_detail_list[n] ) end end return 0, combine_result end local function cg_detail_add_alloc_qty( strLuaDEID, d_cntr_detail_list ) local m, nRet, strRetInfo for m = 1, #d_cntr_detail_list do -- CG_Detail 加分配量 strCondition = "S_ID = '"..d_cntr_detail_list[m].cg_detail_id.."'" nRet, strRetInfo = mobox.incDataObjAccQty ( strLuaDEID, "CG_Detail", strCondition, "F_QTY", "F_ALLOC_QTY", lua.Get_NumAttrValue( d_cntr_detail_list[m].qty ) ) if ( nRet ~= 0 or strRetInfo ~= '') then return 1, '在增加【容器货品明细】中的分配量时失败!'..strRetInfo end end return 0 end function main( strLuaDEID ) local nRet, strRetInfo -- 获取出库波次对象属性 nRet, strRetInfo = mobox.getCurEditDataObjAttr( strLuaDEID, "S_WAVE_NO", "N_B_STATE","S_WH_CODE","S_AREA_CODE","S_STATION_NO" ) local input_value = json.decode( strRetInfo ) local wave_no = lua.Get_StrAttrValue( input_value[1].value ) local b_state = lua.Get_NumAttrValue( input_value[2].value ) local wh_code = lua.Get_StrAttrValue( input_value[3].value ) local area_code = lua.Get_StrAttrValue( input_value[4].value ) local station = lua.Get_StrAttrValue( input_value[5].value ) local err_msg = '' local n, strCondition, m local item_list = {} local d_cntr_list = {} -- 配盘/Distribution_CNTR local d_cntr_detail_list = {} -- 配盘明细/Distribution_CNTR_Detail local oo_no_set = {} local loc_code nRet, loc_code = wms_station.Get_Station_Loc( strLuaDEID, station ) if ( nRet ~= 0 ) then lua.Stop( strLuaDEID, loc_code ) return end local detail_attrs, ow_compose local outbound_detail_list local data_objs local pickbox_volume = wms_base.Get_nConst( strLuaDEID, "拣料箱体积") local oo_obj_attrs local pick_box_num local pick_box_code local new_d_cntr_detail_list = {} -- 加了拣料箱编码后的 【配盘明细】 local paramter = {} local exit_loc nRet, exit_loc = wms_wh.GetLocInfo( loc_code ) if ( nRet ~= 0 ) then err_msg = '获取站台货位信息失败! '..loc_code goto set_state_ret end if ( wave_no == '' ) then err_msg = '出库波次编码不能为空!' goto set_state_ret end if ( wh_code == '' ) then err_msg = '出库波次中仓库编码不能为空!' goto set_state_ret end -- 获取 出库波次 的出库单组成,生成一个 oo_no_set 对象,["OW01","OW02",...] strCondition = "S_WAVE_NO = '"..wave_no.."'" nRet, data_objs = m3.QueryDataObject(strLuaDEID, "OW_Compose", strCondition, "S_OO_NO" ) if (nRet ~= 0) then return 2, "QueryDataObject失败!"..data_objs end if ( data_objs == '' ) then -- 设置错误信息 err_msg = "波次号'"..wave_no.."'的出库波次没有组成对象!" goto set_state_ret end for n = 1, #data_objs do ow_compose = m3.KeyValueAttrsToObjAttr(data_objs[n].attrs) table.insert( oo_no_set, ow_compose.S_OO_NO ) end -- 获取 出库波次 货品明细, 生成 item_list 需要出库的货品清单 strCondition = "S_WAVE_NO = '"..wave_no.."'" nRet, data_objs = m3.QueryDataObject(strLuaDEID, "OW_Detail", strCondition, "N_ROW_NO" ) if (nRet ~= 0) then return 2, "QueryDataObject失败!"..data_objs end if ( data_objs == '' ) then -- 设置错误信息 err_msg = "波次号'"..wave_no.."'的出库波次明细为空!" goto set_state_ret end for n = 1, #data_objs do detail_attrs = m3.KeyValueAttrsToObjAttr(data_objs[n].attrs) local item = { item_code = lua.Get_StrAttrValue( detail_attrs.S_ITEM_CODE ), item_name = lua.Get_StrAttrValue( detail_attrs.S_ITEM_NAME ), volume = lua.Get_NumAttrValue( detail_attrs.F_VOLUME), weight = lua.Get_NumAttrValue( detail_attrs.F_WEIGHT), cell_type = lua.Get_StrAttrValue( detail_attrs.S_CELL_TYPE ), qty = lua.Get_NumAttrValue( detail_attrs.F_QTY), alloc_qty = 0, cntr_cell_list = {}, ok = false -- true 表示这个货品已经配完货 } table.insert( item_list, item ) end -- 【step1】 系统自动配货算法 -- wms_out.Distribution_Procedure 是汉和WMS的一个标准的配盘算法 -- MDF BY WHB 20250110 area_code 改成空(原因是可以在输送线上获取容器,否则就只能是存储区匹配) paramter = { wh_code = wh_code, area_code = '', exit_loc = exit_loc, bs_type = "Outbound_Wave", bs_no = wave_no, order_by = "S_BATCH_NO", station = station } nRet, strRetInfo = wms_out.Distribution_Procedure( strLuaDEID, item_list, paramter, d_cntr_list, d_cntr_detail_list, 1 ) if ( nRet ~= 0 ) then err_msg = "【step1】系统自动配货算法,详细信息见日志!"..strRetInfo lua.Debug( strLuaDEID, debug.getinfo(1), "【step1】系统自动配货算法出错", "wms_out.Distribution_Procedure 返回错误: "..strRetInfo ) goto set_state_ret end lua.Debug( strLuaDEID, debug.getinfo(1), "配盘 --> ", d_cntr_list ) lua.Debug( strLuaDEID, debug.getinfo(1), "配盘明细1 --> ", d_cntr_detail_list ) -- CG_Detail 加分配量 nRet, strRetInfo = cg_detail_add_alloc_qty( strLuaDEID, d_cntr_detail_list ) if ( nRet ~= 0 ) then err_msg = "【step1.1】CG_Detail 加分配量失败,详细信息见日志!"..strRetInfo lua.Debug( strLuaDEID, debug.getinfo(1), "【step1.1】CG_Detail 加分配量失败", strRetInfo ) goto set_state_ret end -- V2.0 -- 配盘明细根据料格合并相同货品数量 nRet, d_cntr_detail_list = combine_cntr_detail( d_cntr_detail_list ) if ( nRet ~= 0 ) then err_msg = "【step1.2】合并配盘明细错误!"..d_cntr_detail_list lua.Debug( strLuaDEID, debug.getinfo(1), "【step1.2】合并配盘明细错误", d_cntr_detail_list ) goto set_state_ret end lua.Debug( strLuaDEID, debug.getinfo(1), "配盘明细 after combine --> ", d_cntr_detail_list ) --【step2】 如果采用波次需要把【配盘明细】根据出库单明细进行批分 -- 因为是采用波次出库,在生成配盘明细的 bs_type 的时候用的是 波次及波次号,在后面需要进一批批分到出库单上 -- 如果没采用波次可以直接用 d_cntr_detail_list 生成数据 -- step2.1 获取 出库单明细全部,如果有多个出库单把这些出库单的明细全部加在 outbound_detail_list nRet, outbound_detail_list = wms_out.Get_Outbound_Detail_list( strLuaDEID, oo_no_set ) if ( nRet ~= 0 ) then err_msg = "【step2.1】获取所有出库单明细时出错,详细信息见日志!" lua.Debug( strLuaDEID, debug.getinfo(1), "【step2.1】获取所有出库单明细时出错", "wms_out.Get_Outbound_Detail_list 返回错误: "..outbound_detail_list ) goto set_state_ret end -- step 2.2 批分出库单明细 d_cntr_detail_list = wms_out.Split_Distribution_CNTR_Detail( strLuaDEID, wave_no, "Outbound_Wave", d_cntr_detail_list, outbound_detail_list ) lua.Debug( strLuaDEID, debug.getinfo(1), "配盘明细2 --> ", d_cntr_detail_list ) -- step 2.3 检查出库单中的拣料箱数量,如果大于1的要根据拣料箱进行批分(明确这些拣料箱放哪些检出的货品) -- HAN 20241031 新增变更 -- 找出波次中拣料箱数量大于1的出库单,这些出库单需要对拣货箱进行批分 strCondition = "S_WAVE_NO = '"..wave_no.."'" nRet, data_objs = m3.QueryDataObject(strLuaDEID, "Outbound_Order", strCondition, "S_NO" ) if (nRet ~= 0) then err_msg = "QueryDataObject失败!" goto set_state_ret end for n = 1, #data_objs do oo_obj_attrs = m3.KeyValueAttrsToObjAttr(data_objs[n].attrs) pick_box_num = lua.Get_NumAttrValue( oo_obj_attrs.N_PICKING_BOX_NUM ) pick_box_code = lua.Get_StrAttrValue( oo_obj_attrs.S_PICK_BOX_CODE ) if (pick_box_code == '' or pick_box_num == 0 ) then err_msg = "出库单'"..oo_obj_attrs.S_NO.."'没有绑定拣料箱!" goto set_state_ret end -- 从 d_cntr_detail_list 获取该出库单号的【配盘明细】 sub_d_cntr_detail_list = get_d_cntr_detail_by_bs_no( d_cntr_detail_list, oo_obj_attrs.S_NO ) if ( pick_box_num == 1 ) then -- 把拣料箱编码加入 d_cntr_detail for m = 1, #sub_d_cntr_detail_list do sub_d_cntr_detail_list[m].pick_box_code = pick_box_code table.insert( new_d_cntr_detail_list, sub_d_cntr_detail_list[m] ) end else nRet, strRetInfo = split_by_picking_box( strLuaDEID, sub_d_cntr_detail_list, pick_box_code, new_d_cntr_detail_list ) lua.Debug( strLuaDEID, debug.getinfo(1), "拣货箱批分后-->", new_d_cntr_detail_list ) if ( nRet ~= 0 ) then err_msg = "出库单'"..oo_obj_attrs.S_NO.."'在批分拣料箱时发生错误, 具体信息请见日志!" lua.Debug( strLuaDEID, debug.getinfo(1), "【step2.3】 批分拣料箱时出错", "split_by_picking_box 返回错误: "..strRetInfo ) goto set_state_ret end end end --【step3】 创建配盘及配盘明细 nRet, strRetInfo = wms_out.Creat_Distribution_list( strLuaDEID, d_cntr_list, new_d_cntr_detail_list ) if ( nRet ~= 0 ) then err_msg = "wms_out.Creat_Distribution_list 发生错误,具体信息请查看系统日志!" lua.Debug( strLuaDEID, debug.getinfo(1), "【step3】 创建配盘及配盘明细出错", "wms_out.Creat_Distribution_list 返回错误: "..strRetInfo ) goto set_state_ret end -- 设置出库波次的状态 ::set_state_ret:: strCondition = "S_WAVE_NO = '"..wave_no.."'" local strSetAttr if ( err_msg ~= '' ) then -- 5 错误 strSetAttr = "N_B_STATE = 5, S_ERR_MSG = '"..lua.FormatSQLString(err_msg).."', N_PRE_B_STATE = "..b_state else -- 2 配货完成 strSetAttr = "N_B_STATE = 2, S_ERR_MSG = ''" end nRet, strRetInfo = mobox.updateDataAttrByCondition( strLuaDEID, "Outbound_Wave", strCondition, strSetAttr ) if ( nRet ~= 0 ) then lua.Error( strLuaDEID, debug.getinfo(1), "更新【出库波次】信息失败!"..strRetInfo ) end if ( err_msg ~= '' ) then mobox.addProcSQL3("Outbound_Wave", strCondition, strSetAttr) mobox.stopProgram( strLuaDEID, "自动配盘失败!"..err_msg ) return end -- 设置出库单状态 = 2 (配货完成) if ( err_msg == '' ) then strSetAttr = "N_B_STATE = 2" nRet, strRetInfo = mobox.updateDataAttrByCondition( strLuaDEID, "Outbound_Order", strCondition, strSetAttr ) if ( nRet ~= 0 ) then lua.Error( strLuaDEID, debug.getinfo(1), "更新【出库单】信息失败!"..strRetInfo ) end -- 触发后台脚本生成出库作业 local strCurEditClsID, strCurEditObjID nRet, strCurEditClsID, strCurEditObjID = mobox.getCurEditDataObjID( strLuaDEID ) if ( nRet ~= 0 ) then lua.Error( strLuaDEID, debug.getinfo(1), "getCurEditDataObjID失败! " ) end local add_wfp = { wfp_type = 1, cls = "Outbound_Wave", obj_id = strCurEditObjID, obj_name = "出库波次'"..wave_no.."'-->生成出库作业", trigger_event = "后台创建出库作业" } nRet, strRetInfo = m3.AddSysWFP( strLuaDEID, add_wfp ) if ( nRet ~= 0 ) then lua.Error( strLuaDEID, debug.getinfo(1), "AddSysWFP失败!"..strRetInfo ) end end end